import ctypes
import json
from lib.lib import *
import shutil
import os
import sys
import traceback
import threading
import platform
import time
from lib.Event import Event, EventHandler
import platform


def get_handler(usr_comm):
    from lib.support_lib import get_info, CustomJsonEncoder
    if not usr_comm:
        raise ValueError("参数不能为空")
    try:
        if usr_comm[0] == "plugins":
            return json.dumps(static["modules"])
        elif usr_comm[0] == "command":
            return json.dumps(list(comm_lib.command_list.keys()))
        elif usr_comm[0] == "info":
            if len(usr_comm) < 2:
                raise ValueError("info 命令需要指定文件名")
            return get_info(f"{static['module_path']}/{usr_comm[1]}.py")
        else:
            obj = eval(usr_comm[0])
            for seg in usr_comm[1:]:
                if isinstance(obj, (dict, list)):
                    obj = obj[seg]
                else:
                    raise AttributeError(f"对象不支持索引访问: {type(obj)}")
            return json.dumps(obj, indent=2, cls=CustomJsonEncoder)
    except (IndexError, KeyError) as e:
        return f"路径访问错误: {e}"
    except Exception as e:
        return f"执行失败: {e}"


def set_handler(usr_comm):
    if len(usr_comm) < 2:
        raise Exception("参数不足")
    data = usr_comm.pop()
    root_obj = eval(usr_comm[0])
    if len(usr_comm) == 1:
        globals()[usr_comm[0]] = data
        return
    parent = root_obj
    path_segments = usr_comm[1:]
    for seg in path_segments[:-1]:
        parent = parent[seg]
    parent[path_segments[-1]] = data


def set_info_handler(usr_comm):
    if len(usr_comm) < 2:
        return "参数不足"
    data_str = usr_comm.pop()
    try:
        data = json.loads(data_str)
    except json.JSONDecodeError:
        data = data_str

    target_path_parts = usr_comm
    if not target_path_parts:
        return "目标路径不能为空"

    root_obj_name = target_path_parts[0]
    if root_obj_name in globals():
        root_obj_container = globals()
    elif root_obj_name in static:
        root_obj_container = static
    elif root_obj_name in loaded_plugins:
        root_obj_container = loaded_plugins
    else:
        return f"错误: 无法找到目标对象 {root_obj_name}"

    if len(target_path_parts) == 1:
        if root_obj_container:
            root_obj_container[root_obj_name] = data
            return "success"
        return f"错误: 无法对 {root_obj_name} 赋值"

    parent = root_obj_container[root_obj_name]
    for i, seg in enumerate(target_path_parts[1:-1]):
        if isinstance(parent, (dict, list)):
            if isinstance(parent, list) and seg.isdigit():
                seg = int(seg)
            parent = parent[seg]
        elif hasattr(parent, seg):
            parent = getattr(parent, seg)
        else:
            return f"错误: 路径 '{' '.join(target_path_parts[:i + 2])}' 无效"

    final_key = target_path_parts[-1]
    if isinstance(parent, (dict, list)):
        if isinstance(parent, list) and final_key.isdigit():
            final_key = int(final_key)
        parent[final_key] = data
    elif hasattr(parent, final_key):
        setattr(parent, final_key, data)
    else:
        return f"错误: 无法对路径 '{' '.join(target_path_parts)}' 赋值"
    return "success"


def get_info_handler(usr_comm):
    from lib.support_lib import get_info, CustomJsonEncoder
    if not usr_comm:
        raise ValueError("参数不能为空")
    try:
        if usr_comm[0] == "plugins":
            return json.dumps(list(loaded_plugins.keys()))
        elif usr_comm[0] == "command":
            return json.dumps(list(comm_lib.command_list.keys()))
        elif usr_comm[0] == "info":
            if len(usr_comm) < 2:
                raise ValueError("info 命令需要指定文件名")
            return get_info(f"{static['module_path']}/{usr_comm[1]}.py")
        else:
            if usr_comm[0] in loaded_plugins:
                obj = loaded_plugins[usr_comm[0]]
            elif usr_comm[0] in globals():
                obj = globals()[usr_comm[0]]
            elif usr_comm[0] in static:
                obj = static[usr_comm[0]]
            else:
                try:
                    obj = eval(usr_comm[0])
                except NameError:
                    raise NameError(f"未知对象或变量: {usr_comm[0]}")

            if obj is None:
                raise ValueError(f"无法找到对象: {usr_comm[0]}")

            for seg in usr_comm[1:]:
                if isinstance(obj, (dict, list)):
                    if isinstance(obj, list) and seg.isdigit():
                        seg = int(seg)
                    obj = obj[seg]
                elif hasattr(obj, seg):
                    obj = getattr(obj, seg)
                else:
                    raise AttributeError(f"对象 {obj} 不支持索引访问或没有属性: {seg}")
            return json.dumps(obj, indent=2, cls=CustomJsonEncoder)
    except (IndexError, KeyError) as e:
        return f"路径访问错误: {e}"
    except Exception as e:
        return f"执行失败: {e}"


def pick_module(name):
    if name.endswith(plugin_suffix):
        return name.split(".")[0]
    else:
        return ""


def unload_module(t_name):
    if t_name not in threads:
        return
    t_id = threads[t_name].ident
    static["running"][t_name] = False
    threads[t_name].join(timeout=5)
    if threads[t_name].is_alive():
        print(f"强制终止线程 {t_name}")
        try:
            res = ctypes.pythonapi.PyThreadState_SetAsyncExc(t_id, ctypes.py_object(SystemExit))
            if res == 0:
                print("Invalid thread id!")
            elif res > 1:
                ctypes.pythonapi.PyThreadState_SetAsyncExc(t_id, 0)
                print("Exception raise failure")
        except Exception as e:
            print(f"Exception stopping module {t_name}: {e}")
        threads[t_name].join(timeout=1)
        if threads[t_name].is_alive():
            print(f"线程 {t_name} 仍存活，可能无法终止")
    del threads[t_name]
    print(f"{t_name} 已退出")


def quit_all():
    try:
        if not is_error:
            log.close()
            os.remove(log.name)
        tmp = threads.copy()
        for thread in tmp:
            try:
                unload_module(thread)
            except Exception as e:
                print(f"Exception closing thread {thread}:")
                print(e)
        # 恢复标准输入输出
        sys.stdout = sys.__stdout__
        sys.stderr = sys.__stderr__
    except Exception as e:
        traceback.print_exc()


def thread_watcher():
    while True:
        for i in list(threads):
            if not threads[str(i)].is_alive():
                threads.pop(i)
                static["running"][str(i)] = False
                err_output.write(f"\rThread {i} closed\n")
                err_output.flush()
                print(f"Thread {i} closed", file=sys.stderr)
                time.sleep(0.1)
                raw_output.write("\r$: ")
                raw_output.flush()
        time.sleep(1)


def check_log():
    num = 0
    while True:
        log_file = f"logs/{time.strftime('%Y-%m-%d', time.localtime())}-{num}.log"
        if not os.path.isfile(log_file):
            return log_file
        else:
            num += 1


def logger(msg):
    if msg not in ["", " ", "\r"]:
        msg.replace("\n", "")
    log.write(msg)
    log.flush()


class err_handler:
    thread_closed = False

    def __init__(self):
        self.old_stm = sys.stderr

    def write(self, msg):
        global is_error
        is_error = True
        if str(msg).startswith("Exception in thread"):
            logger(msg)
            self.thread_closed = True
        elif self.thread_closed:
            thread = str(msg)
            self.old_stm.write(f"\rThread {thread} raised an exception\n")
            self.old_stm.flush()
            time.sleep(0.1)
            raw_output.write("$: ")
            raw_output.flush()
            logger(msg)
            self.thread_closed = False
        else:
            logger(msg)

    def flush(self):
        self.old_stm.flush()


class log_handler:
    def __init__(self):
        self.old_stm = sys.stdout

    def write(self, msg):
        self.old_stm.write('\r' + msg)
        logger(msg)

    def flush(self):
        self.old_stm.flush()


def command_handler(usr_comm):
    from lib.support_lib import git_pull, load_module
    if usr_comm[0] == "quit":
        try:
            if usr_comm[1] in threads:
                unload_module(usr_comm[1])
            else:
                return "未知模块"
        except IndexError:
            quit_all()
            print("主程序终止")
            os._exit(0)
    elif usr_comm[0] == "boot":
        return load_module(static["module_path"], usr_comm[1])
    elif usr_comm[0] == "command":
        if usr_comm[1] == "unregister":
            return comm_lib.unregister(usr_comm[2])
        else:
            return "未知指令(unregister)"
    elif usr_comm[0] == "reload":
        quit_all()
        executable = sys.executable
        args = sys.argv[:]
        args.insert(0, executable)
        os.execvp(executable, args)
    elif usr_comm[0] == "upgrade":
        if static["online"]:
            result = git_pull()
            print(result)
            if result.find("Already up to date.") == -1:
                print("Rebooting in 1 sec...")
                sleep(1)
                command_handler(["reload"])
            return None
        else:
            return "离线模式无法更新"
    elif usr_comm[0] == "del":
        if usr_comm[1] == "logs":
            global log
            log.close()
            shutil.rmtree('logs')
            os.mkdir('logs')
            log = open(check_log(), "w+", encoding="utf-8")
        return None
    else:
        return "未知指令(quit/boot/upgrade/command/reload/del)"


plugin_suffix = "py"
is_error = False
config = Config
pip_mirror = ''
config_file = 'config/main.json'
default = {
    "hostname": platform.node(),
    "Disabled": ["module_template"],
    "online": True,
    "debugging": False,
    "pip_mirror": "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple",
    "git_mirror": "https://ghproxy.com/https://github.com",
    "portable_git": "WIP",
}
event_handler = EventHandler()


def main():
    global log, pip_mirror

    if not os.path.exists('logs'):
        print("正在创建日志文件夹")
        os.mkdir('logs')
    if not os.path.exists('config'):
        print("正在创建配置文件夹")
        os.mkdir('config')
    config = Config("core", default)
    log = open(check_log(), "w+", encoding="utf-8")

    sys.stdout = log_handler()
    sys.stderr = err_handler()

    os_info = platform.system()
    os_version = platform.version()
    python_version = platform.python_version()
    if python_version[0] != '3':
        print(f"Python version {python_version} not supported")
        exit(-1)
    static["hostname"] = config.conf["hostname"]
    static["SYS_INFO"] = os_info
    static["SYS_VER"] = os_version
    static["PY_EXEC"] = sys.executable
    static["PY_VER"] = python_version
    static["running"] = {}
    static["modules"] = {"core": None}
    static["online"] = config.conf['online']
    static["debugging"] = config.conf['debugging']
    static["running"]["core"] = False
    static["event_handler"] = event_handler

    if static["debugging"] is True:
        sys.stdout = raw_output
        sys.stderr = err_output

    print(f"System: {os_info}")
    print(f"Python: {python_version}")
    print(f"Disabled: {config.conf['Disabled']}")

    from lib.support_lib import get_info, check_git, install_git, load_module, load_static

    static["module_path"] = os.path.join("module")
    sys.path.append(static["module_path"])
    module_files = os.listdir(static["module_path"])
    modules = map(pick_module, module_files)
    modules = [_ for _ in modules if _ != ""]
    for name in modules:
        info = get_info(f"{static['module_path']}/{name}.py")
        if info: static["modules"][info["id"]] = info.get("version", None)
        if name not in config.conf['Disabled']:
            load_module(static["module_path"], name)

    static["static_path"] = os.path.join("static_module")
    sys.path.append(static["static_path"])
    static_files = os.listdir(static["static_path"])
    static_plugins = map(pick_module, static_files)
    static_plugins = [_ for _ in static_plugins if _ != ""]
    for name in static_plugins:
        info = get_info(f"{static['static_path']}/{name}.py")
        if info: static["modules"][info["id"]] = info.get("version", None)
        if name not in config.conf['Disabled']:
            load_static(static["static_path"], name)
    static["running"]["core"] = True
    if check_git():
        static["git_available"] = True
    else:
        static["git_available"] = False
    if not static["git_available"]:
        if static["online"] and static["SYS_INFO"] in ["Windows"]:
            print("未检测到Git环境，正在尝试自动安装")
            if install_git():
                print("Git安装成功")
            else:
                print("Git安装失败，某些功能可能不可用")
        else:
            print("未检测到Git环境，某些功能可能不可用")

    print("Loaded:", list(threads.keys()))
    watchdog = threading.Thread(target=thread_watcher, name="watch_dog", daemon=True)
    watchdog.start()
    print("watch_dog started")
    comm_lib.register("core", command_handler)
    comm_lib.register("get", get_handler)
    comm_lib.register("set", set_handler)
    while 1:
        try:
            raw_output.write("$: ")
            raw_output.flush()
            print(command(input().split(" ")))
        except KeyboardInterrupt:
            quit_all()
            print("主程序终止")
            quit()


if __name__ == '__main__':
    main()
